package com.biz.primus.ms.payment.service;

import com.biz.primus.base.enums.CommonStatus;
import com.biz.primus.base.exception.BizSilentException;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.common.utils.CopyUtils;
import com.biz.primus.common.vo.SpringPageVO;
import com.biz.primus.model.payment.enums.PaymentExceptionEnum;
import com.biz.primus.model.payment.enums.PaymentTradeType;
import com.biz.primus.model.payment.enums.PaymentType;
import com.biz.primus.model.payment.vo.req.BalanceQueryReqVo;
import com.biz.primus.model.payment.vo.req.PaymentChannelQueryVo;
import com.biz.primus.model.payment.vo.req.PaymentChannelReqVo;
import com.biz.primus.model.payment.vo.resp.BalanceQueryRespVo;
import com.biz.primus.model.payment.vo.resp.PaymentChannelQueryResVo;
import com.biz.primus.model.payment.vo.resp.PaymentChannelResVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.base.vo.SpringPageToSpringPageVO;
import com.biz.primus.ms.payment.dao.po.Merchant;
import com.biz.primus.ms.payment.dao.po.PaymentChannel;
import com.biz.primus.ms.payment.dao.repository.PaymentChannelRepository;
import com.biz.primus.ms.payment.dao.specification.PaymentChannelQueryVoToSpecification;
import com.biz.primus.ms.payment.event.NewPaymentChannelEvent;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;

import static com.biz.primus.model.payment.enums.PaymentExceptionEnum.*;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.String.format;

/**
 * 支付渠道
 * Created by defei on 9/25/17.
 */
@Slf4j
@Service
public class PaymentChannelService extends AbstractBaseService {

    private final PaymentChannelRepository repository;

    private final MerchantService merchantService;

    @Autowired
    public PaymentChannelService(PaymentChannelRepository repository, MerchantService merchantService) {
        this.repository = repository;
        this.merchantService = merchantService;
    }

    /**
     * 添加一个支付渠道
     *
     * @param reqVo {@link PaymentChannelReqVo}
     * @return {@link PaymentChannelResVo}
     * @throws BizSilentException {@link PaymentExceptionEnum#ILLEGAL_PARAMETER} || {@link
     * PaymentExceptionEnum#MERCHANT_NOT_FOUND} || {@link PaymentExceptionEnum#PAYMENT_CHANNEL_EXIST}
     */
    @Transactional
    public PaymentChannelResVo create(PaymentChannelReqVo reqVo) throws BizSilentException {

        AssertUtils.notNull(reqVo, ILLEGAL_PARAMETER, "Argument reqVo is null.");
        AssertUtils.notNull(reqVo.getPaymentType(), ILLEGAL_PARAMETER, "Argument reqVo.paymentType is null.");
        Long merchantId = reqVo.getMerchantId();
        Merchant merchant = merchantService.findOne(merchantId);

        PaymentChannel existPaymentChannel = repository.findByMerchantIdAndPaymentTypeAndPaymentTradeType(merchantId, reqVo.getPaymentType(), reqVo.getPaymentTradeType());
        AssertUtils.isNull(existPaymentChannel, PAYMENT_CHANNEL_EXIST, format("PaymentChannel exist for merchant:%s and payment type:%s", merchantId, reqVo.getPaymentType()));

        PaymentChannel target = new PaymentChannel();
        BeanUtils.copyProperties(reqVo, target);
        target.setMerchant(merchant);
        PaymentChannel savedPaymentChannel = repository.save(target);
        return toPaymentChannelResVo(savedPaymentChannel);
    }

    /**
     * 获取支付渠道信息
     *
     * @param paymentChannelId 支付渠道id
     * @return {@link PaymentChannelResVo}
     * @throws BizSilentException {@link PaymentExceptionEnum#PAYMENT_CHANNEL_NOT_FOUND}
     */
    public PaymentChannelResVo getPaymentChannelInfo(Long paymentChannelId) throws BizSilentException {

        PaymentChannel paymentChannel = findOne(paymentChannelId);

        PaymentChannelResVo targetResVo = new PaymentChannelResVo();
        BeanUtils.copyProperties(paymentChannel, targetResVo);
        return targetResVo;
    }

    /**
     * 获取支付渠道信息
     *
     * @param mchId 商户id
     * @param paymentType 渠道类型
     */
    public PaymentChannelResVo getPaymentChannelInfo(Long mchId, PaymentType paymentType, PaymentTradeType paymentTradeType) {

        PaymentChannel existPaymentChannel = repository.findByMerchantIdAndPaymentTypeAndPaymentTradeType(mchId, paymentType, paymentTradeType);
        AssertUtils.notNull(existPaymentChannel, PAYMENT_CHANNEL_NOT_FOUND, format("PaymentChannel not found for merchant:%s and payment type:%s", mchId, paymentType));
        return toPaymentChannelResVo(existPaymentChannel);
    }

    /**
     * 获取支付渠道持久对象
     *
     * @param paymentChannelId 支付渠道id
     * @return {@link PaymentChannel}
     * @throws BizSilentException 如果参数为空或无效
     */
    PaymentChannel findOne(Long paymentChannelId) throws BizSilentException {

        AssertUtils.notNull(paymentChannelId, ILLEGAL_PARAMETER, "Argument paymentChannelId is null.");

        PaymentChannel target = repository.findOne(paymentChannelId);
        AssertUtils.notNull(target, PAYMENT_CHANNEL_NOT_FOUND, format("PaymentChannel not found by id:%s", paymentChannelId));

        return target;
    }

    /**
     * 按条件分页查询
     *
     * @param queryVo {@link PaymentChannelQueryVo}
     */
    public SpringPageVO<PaymentChannelResVo> findAll(PaymentChannelQueryVo queryVo) {

        PageRequest pageable = queryVo.toPageRequest();
        Page<PaymentChannel> page = repository.findAll(new PaymentChannelQueryVoToSpecification(queryVo), pageable);
        return SpringPageToSpringPageVO.of(page, input -> CopyUtils.copyProperties(input, new PaymentChannelResVo()));
    }

    /**
     * 更新支付渠道信息
     *
     * @param reqVo {@link PaymentChannelReqVo}
     * @return {@link PaymentChannelResVo}
     * @throws BizSilentException {@link PaymentExceptionEnum#ILLEGAL_PARAMETER} || {@link
     * PaymentExceptionEnum#PAYMENT_CHANNEL_NOT_FOUND}
     */
    @Transactional
    public PaymentChannelResVo update(PaymentChannelReqVo reqVo) throws BizSilentException {

        AssertUtils.notNull(reqVo, ILLEGAL_PARAMETER, "Argument reqVo is null.");
        AssertUtils.notNull(reqVo.getStatus(), ILLEGAL_PARAMETER, "Argument reqVo.status is null.");
        PaymentChannel paymentChannel = findOne(reqVo.getId());

        paymentChannel.setParam(reqVo.getParam());
        paymentChannel.setRemark(reqVo.getRemark());
        paymentChannel.setStatus(reqVo.getStatus());
        PaymentChannel savedPaymentChannel = repository.save(paymentChannel);
        return toPaymentChannelResVo(savedPaymentChannel);
    }

    private PaymentChannelResVo toPaymentChannelResVo(PaymentChannel paymentChannel) {
        PaymentChannelResVo paymentChannelResVo = new PaymentChannelResVo();
        BeanUtils.copyProperties(paymentChannel, paymentChannelResVo);
        //TODO 解析channelMchId
        return paymentChannelResVo;
    }


    public List<PaymentChannelQueryResVo> findAvailablePaymentChannelInfoByMchId(String mchId) {

        AssertUtils.hasText(mchId, ILLEGAL_PARAMETER, "Argument mchId is null.");
        List<PaymentChannel> paymentChannels = repository.findByMerchantIdAndStatus(mchId, CommonStatus.ENABLE);
        if(CollectionUtils.isEmpty(paymentChannels)){
            merchantService.findOne(Long.valueOf(mchId));
            return newArrayList();
        }
        return paymentChannels.stream().map(input -> input == null ? null: new PaymentChannelQueryResVo(input.getPaymentType(), input.getPaymentTradeType())).collect(Collectors.toList());
    }

    public BalanceQueryRespVo balanceQuery(BalanceQueryReqVo reqVo) {
        //TODO
        return null;
    }

    /**
     * 事务提交成功之后才触发的事件监听器:@TransactionalEventListener
     */
    @TransactionalEventListener
    void listenNewCatEvent(NewPaymentChannelEvent event) {
        log.info("listen event: {}", event);
    }
}
